home *** CD-ROM | disk | FTP | other *** search
Wrap
var EXPORTED_SYMBOLS = ["YOONO_BKM"]; Components.utils.import("resource://yoono/yoonoPrefs.js"); var yoono = {}; var log = {info:function() {},debug:function() {},warn:function(){},error:function (){},fatal:function(){}}; // Globals const var CI = Components.interfaces; var CL = Components.classes; const OBS=CL['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService); const DIRSERVICE = CL['@mozilla.org/file/directory_service;1'].getService(CI.nsIProperties); const MAX_PATH_LENGTH = 5000; const LIVETAG = '.LIV'; const TOOLBARNAME = "PERSONAL_BOOKMARK_FOLDER"; const PRIVATEFILE = "yoonoprivatefolders.txt"; const YOONO_DIR = "yoono"; const BACKUP_DIR = "yoonobookmarkbackups"; const HISTORYPREF = "extensions.yoono.bookmarks.maxhistory"; const BACKUPS_NUMBER = 5; const PREFS = CL['@mozilla.org/preferences-service;1'].getService(CI.nsIPrefBranch); /*** Globals var ***/ var gCurrentTree = null; var gCurrentNode = null; var nbBkms = 1; var gBkmById = {}; var gBkmByUrl = {}; /***************************************************/ /*** Browser specific version Javascript loading ***/ var restore={}; try { // Load version specific JS from : chrome/$ff-version$/* var loader = CL["@mozilla.org/moz/jssubscript-loader;1"].createInstance(CI.mozIJSSubScriptLoader); loader.loadSubScript("chrome://yoonospecific/content/bookmarks.js"); loader.loadSubScript("chrome://yoonospecific/content/bookmarks-restore.js",restore); } catch(e) { var console = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService); var scriptError = CL["@mozilla.org/scripterror;1"].createInstance(CI.nsIScriptError); scriptError.init("Error loading specific code >>> "+e+" <<<\n"+e.stack, e.filename, null, e.lineNumber, null, scriptError.errorFlag, ""); console.logMessage(scriptError); } function Bookmarks() { this.wrappedJSObject=this; } /********************************/ /*** Component initialization ***/ Bookmarks.prototype.start = function (y) { try { yoono=y; log=y.log; log.debug("load bookmarks component"); function launch() { privateMgr.init(); // User already registered and not anonymous if (YOONO_PREFS.get('userid')) { var aIsModified = YOONO_PREFS.storeLastModified("init"); var aSyncMode = ( aIsModified == true ? "manual-sync" : "auto-sync" ); if (YOONO_PREFS.get('nosynchro')) aSyncMode = 'no-sync'; if(-1 == YOONO_PREFS.get('userid').indexOf(':')) { aSyncMode = 'no-sync'; } yoono.server.launch('connect',aSyncMode); // on se connecte apres avoir charge les marque-pages } // Disable backup on each firefox startup //yoono.bkm.backup(); } this.initBackups(); // Launch version specific loading start(launch); } catch(e) { log.exception(e); this.dump(); } } Bookmarks.prototype.uninstall = function () { privateMgr.uninstall(); } /* Bookmarks.prototype.mergeBookmarksWithServer = function() { YOONO_PREFS.set('synchroaction', "merge"); // The load-all-links command is always processed last, whatever its position in the request yoono.server.launch('load-all-links'); } Bookmarks.prototype.importBookmarksFromServer = function() { YOONO_PREFS.set('synchroaction', "import"); // The load-all-links command is always processed last, whatever its position in the request yoono.server.launch('load-all-links'); } Bookmarks.prototype.exportBookmarksOnServer = function() { yoono.server.launch('save-all-links'); } */ /***********************/ /*** Backups service ***/ Bookmarks.prototype.initBackups = function () { // Search backup dir this.backupDir = DIRSERVICE.get("ProfD",CI.nsIFile); this.backupDir.append(BACKUP_DIR); if(!this.backupDir.exists()) this.backupDir.create(CI.nsIFile.DIRECTORY_TYPE, 0755); } Bookmarks.prototype.setMaxBackups = function (n) { PREFS.setIntPref(HISTORYPREF,n); this.flushBackups(); } Bookmarks.prototype.getMaxBackups = function () { return PREFS.getIntPref(HISTORYPREF); } Bookmarks.prototype.flushBackups = function () { // Remove one file, if we reach backups limit while(true) { var e = this.backupDir.directoryEntries; var older=null; var nbFiles=0; while(e.hasMoreElements()) { var file=e.getNext().QueryInterface(Components.interfaces.nsIFile); if (!file.isFile()) continue; if (!older || file.lastModifiedTime < older.lastModifiedTime) { older=file; } nbFiles++; } if (nbFiles>this.getMaxBackups()) older.remove(false); else break; } } Bookmarks.prototype.backup = function () { try { // Create a new backup file var d=new Date(); var file = this.backupDir.clone(); file.append(d.getFullYear()+"_"+(d.getMonth()+1)+"_"+d.getDate()+"-"+d.getHours()+"h"+d.getMinutes()+"m"+d.getSeconds()+"s.html"); // Build backup! restore.backup(file); this.flushBackups(); } catch(e) { log.exception(e); } } Bookmarks.prototype.restore = function (name) { // Disable server synch Server2Browser.working=true; // Launch restore log.debug("START RESTORE"); var file=this.backupDir.clone(); file.append(name); restore.restore(file); log.debug("END RESTORE"); // Export all bkms yoono.server.launch('save-all-links'); // Reenable server synch Server2Browser.working=false; } Bookmarks.prototype.getBackupList = function (file) { var e = this.backupDir.directoryEntries; var backups=[] while(e.hasMoreElements()) { var file=e.getNext().QueryInterface(Components.interfaces.nsIFile); if (!file.isFile()) continue; backups.push(file); } backups.sort(function (a,b) {return a.lastModifiedTime - b.lastModifiedTime;}); var list=[]; for(var i=0;i<backups.length;i++) { var file=backups[i]; var m=file.leafName.match(/(\d+)_(\d+)_(\d+)-(\d+)h(\d+)m(\d+)s\.html/); if (m) { list.push({file:file, name:file.leafName, date:new Date(m[1],parseInt(m[2]) - 1,m[3],m[4],m[5],m[6])}); } else file.remove(false); } return list; } /*** Debuging ***/ Bookmarks.prototype.dump = function () { log.info("Bookmark tree : \n"+gCurrentTree.toString()); } Bookmarks.prototype.bookmarksNumber = function () { var c=0; for each(var n in gBkmById) { if (!n.isFolder()) { c++; } } return c; } /************************/ /*** Common interface ***/ Bookmarks.prototype.loadAllLinks = function (links) { try { return Server2Browser.loadAllLinks(links); } catch(e) { log.exception(e); } } Bookmarks.prototype.getSyncId = function () { return gCurrentTree?gCurrentTree.getSyncid():""; } Bookmarks.prototype.getCurrentTree = function () { return gCurrentTree; } Bookmarks.prototype.getNbBkms = function () { return nbBkms; } Bookmarks.prototype.isPrivate = function (id) { if (gBkmById[id]) return gBkmById[id].isPrivate(); return false; } Bookmarks.prototype.addToPrivateList = function (id) { privateMgr.addToList(id); } Bookmarks.prototype.removeFromPrivateList = function (id) { privateMgr.removeFromList(id); } Bookmarks.prototype.getNode = function (id) { return gBkmById[id]; } /**************************/ /*** Specific interface ***/ // TODO : check utility of each function // Returns full boookmark list as an array Bookmarks.prototype.getFullListUrl = function () { var urlList = []; for each (var node in gBkmById) { if (node.isFolder() || !node.getUrl()) continue; urlList.push(node.getUrl()); } return urlList; } // Returns array of url contained in the folder whose id is passed Bookmarks.prototype.getNodeListUrl = function(nodeId) { var node = this.getNode(nodeId); if (node) return(this.getUrlsInSubFolder(node)); else // we may select places top container "all bookmarks" which are not tracked by this component return this.getFullListUrl(); } Bookmarks.prototype.getUrlsInSubFolder = function(node) { var obj = { list : [], onNode : function (node) { if(!node.isFolder()) { this.list.push(node.getUrl()); } } } node.traverseNode(obj, 'force'); return obj.list; } Bookmarks.prototype.isKnownUrl = function (url) { if (gBkmByUrl[url]) return true; else return false; } Bookmarks.prototype.addBkmAtRoot = function (url, title) { gBrowserBkm.createBookmark(title, url, gCurrentTree); } /******************************/ /*** Old code compatibility ***/ // TODO : remove all call Bookmarks.prototype.getNodesForTreeView = function (node, level) { log.backtrace("Deprecated call!"); // initialisation if (!node) var node = gCurrentTree; if (!node) return {open:false, name:'none', children: []}; if (!level) var level = 0; var obj = new Object(); obj.open = false; obj.level = level; obj.node = node; obj.published = false; obj.origPublished = false; // to save original state to detect changes obj.private = node.isPrivate(); obj.parent = null; obj.origPrivate = node.isPrivate(); // to save original state to detect changes obj.id = BKMSERV.bookmarksMenuFolder; obj.children = []; return obj; } // END of public interface! // ////////////////////////////// /* // TODO : make BkmsObserver specific to FF3 Browser2Server = { notifyServer : function( cmd, arg ) { if (Server2Browser.isWorking()) return; yoono.server.addToNextCommandScript(command, arg); }, itemAdded : function (node) { if (node.isAcceptable() && !node.isFolder()) this.notifyServer('add-link', node); }, itemRemoved : function (node) { if (node.isAcceptable()) this.notifyServer('remove-link', node); }, itemChanged : function(oldNode, newNode) ( if (newNode.update() && newNode.isAcceptable()) this.notifyServer('update-link', [oldNode, newNode]); }, itemMoved : function(node) { } }; */ //////////////////////////// // BkmsObserver // :: observe bookmarks modifications var BkmsObserver = { _working : false, onBeginUpdateBatch: function() { log.debug("Begin update batch"); this._working = true; }, onEndUpdateBatch: function() { log.debug("End update Batch"); this._working = false; }, notifyServer : function(command, node) { if (Server2Browser.isWorking()) return; if (this._working) { yoono.server.addToNextCommandScript(command, node); } else { yoono.server.launch(command, node); } }, onItemAdded: function(id, folder, index) { //log.debug("add item id : "+id); var parent=gBkmById[folder]; if (!parent) { //log.debug("Ignore bookmarks with parent not in our tree"); return ; } if (parent.isLive()) { //log.debug("we ignore subitem of live bookmarks"); return; } if (gBkmById[id]) { var node=gBkmById[id]; } else { var node=new bkmNode(id, parent); parent.addChild(node); } //log.debug("parent : "+parent.getName()+" name:"+node.getName()+" isFolder:"+node.isFolder()); // Delay creation, because livemarks are really livemarks some times later :/ (ie LIVESERV.isLivemark()->true) [FF3] // (they are first folder, then livemark) // (they are valid livemarks when we receive itemChanged on feedURI property) if (!Server2Browser.isWorking()) { var _this=this; var callback = { notify : function(timer) { try { if (node.isAcceptable() && !node.isFolder()) // we must delay isAcceptable because we check isFolder into... _this.notifyServer('add-link', node); } catch(e) { log.exception(e); } } }; var timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer); timer.initWithCallback(callback, 1000 , timer.TYPE_ONE_SHOT); } return node; }, onItemRemoved: function(id, folder, index) { try { //log.debug("remove item : "+id); var node=gBkmById[id]; if (!node) { //return log.debug("removed node not tracked"); return; } if (node.isFolder()) { var children = node.getAllChildren(); for each(var child in children) { child.remove(); if (child.isAcceptable()) this.notifyServer('remove-link', child); } } else { if (node.isAcceptable()) this.notifyServer('remove-link', node); } node.remove(); } catch(e) { log.exception(e); yoono.bkm.dump(); } }, onItemMoved: function(id, oldParent, oldIndex, newParent, newIndex) { try { if (oldParent == newParent) return log.debug("order moves are ignored"); log.debug("move item : "+id+" parentId:"+newParent); var node=gBkmById[id]; // For unknown bookmark, check if destination is known if (!node) { if (gBkmById[newParent]) { return this.onItemAdded(id, newParent, newIndex); } else { return log.debug("moved node not tracked (may be in private)"); } } log.debug("oldparent:"+oldParent+" > newparent:"+newParent); if (node.isFolder()) { // First save a copy of old children var childs=[]; node.traverseNode({onNode:function(child){ if (!child.isFolder()) childs.push(child.clone()); }}); // Update originals node.getParent().removeChild(node); var dest=gBkmById[newParent]; dest.addChild(node); node.update(dest); // Send requests for each(var child in childs) { var newChild=gBkmById[child.getId()]; log.debug("oldpath:"+child.getPath().toServer+" > newpath: "+newChild.getPath().toServer); if (newChild.isAcceptable()) this.notifyServer('update-link', [child, newChild]); } } else { var oldNode=node.clone(); node.getParent().removeChild(node); var dest=gBkmById[newParent]; dest.addChild(node); node.update(dest); log.debug("oldpath:"+oldNode.getPath().toServer+" > newpath: "+node.getPath().toServer); if (node.isAcceptable()) this.notifyServer('update-link', [oldNode, node]); } } catch(e) { log.exception(e); yoono.bkm.dump(); } }, onItemChanged: function(id, property, isAnnotationProperty, value) { try { if ( ['title', 'uri', 'livemark/feedURI'].indexOf(property) == -1 ) { //log.info("only title, uri and feedURI properties are tracked (not "+property+")"+id+" > "+(gBkmById[id]?gBkmById[id].isFolder():"/")); return; } //log.debug("change item : "+id+"/"+property+"/"+isAnnotationProperty+"/"+value); var node=gBkmById[id]; ////// if (node.isFolder() && property=="title") { // First save a copy of old children var childs=[]; node.traverseNode({onNode:function(child){ if (!child.isFolder()) childs.push(child.clone()); }}); // Update originals node.update(); // Send requests for each(var child in childs) { var newChild=gBkmById[child.getId()]; log.debug("oldpath:"+child.getPath().toServer+" > newpath: "+newChild.getPath().toServer); if (newChild.isAcceptable()) this.notifyServer('update-link', [child, newChild]); } } else { var oldNode=node.clone(); if (node.update() && node.isAcceptable()) { // See onItemAdded ... if (property=="livemark/feedURI" && !oldNode.getUrl()) return log.debug("we ignore first livemark url notification"); this.notifyServer('update-link', [oldNode, node]); } else log.error("not acceptable ? no update ? "); } } catch(e) { log.exception(e); yoono.bkm.dump(); } }, onItemVisited: function(id, visitID, time) { }, QueryInterface: function(iid) { if (iid.equals(Ci.nsINavBookmarkObserver) || iid.equals(Ci.nsISupports)) { return this; } throw Components.results.NS_ERROR_NO_INTERFACE; } }; ////////////////////////////// ////////////////////////////// // Server2Browser // :: handle info from server and sync with rdf view // :: initial loading of all bookmarks from server var Server2Browser = { isWorking : function() {return this.working;}, // la liste des marque-pages pr�sents dans l'ordi notOnServer : {}, // une liste des chemins deja calcules foldersList : {}, // list of incoming private folders privateIncomingFoldersList : {}, // List of folders that will have to be added to private folder file newPrivateFolders : {}, // List of links that where in a browser private folder that the synchro sets public linksBecomingPublic : {}, /* * List all private folders marked as such in incoming folder list */ listPrivateIncomingFolders : function (linkList) { var key = ''; for each (var addLink in linkList) { var privateFolder = addLink.@['private'].toString(); if('CLIENT' == privateFolder) { key = addLink.@path.toString(); log.debug('Private incoming folder found : ' + key); this.privateIncomingFoldersList[key] = {exists: false} ; } } }, /* * manages the list of folders that where in a browser private folder that the synchro * now sets to public * @param node: folder that becomes public */ addLinksBecomingPublic : function(node) { var add = { onNode : function() {} }; add.onNode = function(node) { if (node.isFolder() ) return; var key = node.getPath().toServer + node.getUrl(); // log.debug("XXX BECOMING PUBLIC : " + key); Server2Browser.linksBecomingPublic[key] = { 'node' : node, 'mustCreate' : false }; }; node.traverseNode(add); }, /* * @param linkList : liste des elements a ajouter a l'arborescence (object xmlList) */ loadAllLinks : function (linkList) { log.debug('begin loadAllLinks'); this.foldersList = {}; this.privateIncomingFoldersList = {}; this.newPrivateFolders = {}; this.linksBecomingPublic = {}; this.notOnServer = {}; this.working = true; var _this=this; gBrowserBkm.runInBatchMode(function () { _this.loadAllInBatched(linkList); }); return this.resp; }, loadAllInBatched : function(linkList) { // on renvoie l'action effectivement effectu�e, et le nombre de marque-pages import�s this.resp = { action : YOONO_PREFS.get('synchroaction'), // nombre reellement crees added : 0, // importes depuis le serveur imported : 0, clearCommands : true }; // List can contain (empty) incoming private folders just to be able not to erase them from the browser // in case they are not marked private yet (sync...) // They have the private="CLIENT" attribute. Parse the list to find them and set them as private this.listPrivateIncomingFolders(linkList); // on pousse tous les noeuds existants dans notOnServer pour detecter les suppressions a faire // (except private folders, either existing or incoming) var aNotOnServerCount = 0; for each (var node in gBkmById) { if (node.isFolder() ) { var path = node.getPath().toServer; // If private folder, not in the incoming private folders list, set it to public if(!this.privateIncomingFoldersList[path] && node.isPrivate()) { log.debug('Path ' + path + ' is now public on server'); var id = node.getId(); // Remove it from list of private folders privateMgr.removeFolderFromList(id); node.setPrivate(false); // This folder may have content that is not yet on the server (added when it was private) // and that must be added, excluding what is already on the server... what a mess :-) // Store its content in a special list that will be used with the notOnServer list to build // the add-link orders (and to leave them on the computer !) this.addLinksBecomingPublic(node); } } // skip private nodes, and other stuff if (!node.isAcceptable()) { continue; } if (node.isFolder() ) { this.foldersList[node.getPath().toServer] = node; } else { var key = node.getPath().toServer + node.getUrl(); if (!this.notOnServer[key]) { this.notOnServer[key] = new Array(); // array to manage duplicates } this.notOnServer[key].push(node); aNotOnServerCount++; } } // Send a global notifications that we are starting a synchro OBS.notifyObservers (this,"yoono-synchro-start",YOONO_PREFS.get('synchroaction')); yoono.bkm.backup(); log.debug('notOnServer.length : '+aNotOnServerCount); this.saveLinks(linkList); log.debug('imported='+this.resp.imported+', added='+this.resp.added+', synchroaction='+YOONO_PREFS.get('synchroaction')); if (YOONO_PREFS.get('synchroaction') == 'import') { if(this.resp.imported > 0) { var length = 0; // Must delete from server links that remain in notOnServer for each (var nodeList in this.notOnServer) { length += nodeList.length; } log.debug('length='+length); if (length) { for (var i in this.notOnServer) { log.debug('i='+i); // Remove bkms that were not on server // except if they used to be in a private folder that became public during this sync if(!this.linksBecomingPublic[i]) { for (var j = this.notOnServer[i].length ; j-- > 0; ) { this.removeBkm(this.notOnServer[i][j]); } } else { // Mark link for addition to server this.linksBecomingPublic[i].mustCreate = true; } delete (this.notOnServer[i]); } } // Create the links that were added to private folders that became public because of the sync log.debug('Adding links that became public :'); var cpt = 0; for (var newNode in this.linksBecomingPublic) { if(this.linksBecomingPublic[newNode].mustCreate) { log.debug('Adding ' + newNode); yoono.server.launch('add-link', this.linksBecomingPublic[newNode].node); } } log.debug(cpt + ' links that became public added.'); } else { log.warn("abnormal situation : invalid import, server returns 0 link. Local bookmarks aren't modified"); this.resp.clearCommands = false; } } // Add to private folder list file the new ones just loaded privateMgr.appendToList(this.newPrivateFolders); aNotOnServerCount=0; for(var key in this.notOnServer) aNotOnServerCount++; if (aNotOnServerCount>0) yoono.server.launch('save-all-links-add', this.notOnServer); this.working = false; this.notOnServer = {}; this.foldersList = {}; // Send a global notifications that we are ending a synchro // Needed by sidebar Wizard to know when to send bkm sharing information OBS.notifyObservers (this,"yoono-synchro-end",YOONO_PREFS.get('synchroaction')); log.debug('end loadAllLinks'); return this.resp; }, /* @param linkList : liste d'element de la forme <link title="aTitle" url="aUrl"/> cree les liens correspondants */ saveLinks : function(linkList) { for each (var addLink in linkList) { try { var pathAsStr = addLink.@path.toString(); var url = addLink.@url.toString(); var title = addLink.@title.toString(); var pos = title.lastIndexOf(LIVETAG); if (pos != -1 && pos == title.length - LIVETAG.length) { title = title.slice(0, -LIVETAG.length); var type = 'l'; } else { var type = 'b'; } if (title.match(/^NOTITLE-/)) title = ''; var key = pathAsStr + url; if (this.notOnServer[key]) { // on prend le premier var node = this.notOnServer[key][0]; // si on recoit une url deja presente avec un titre different, // on ecrase le titre existant au lieu de dupliquer le mp if (node.getName() != title) { gBrowserBkm.rename(node,title); } // on supprime les doublons for (var i = this.notOnServer[key].length ; i-- > 1; ) { this.removeBkm(this.notOnServer[key][i]); } // Since the bkm exists on the server, remove it from the list delete (this.notOnServer[key]); } else { // new or modified bkm this.checkOrCreateBkm(addLink, pathAsStr, title, url, type); } this.resp.imported++; } catch(e) { log.exception(e); } } }, checkOrCreateBkm : function(addLink, pathAsStr, title, url, type) { var folder; var privateFolder = addLink.@['private'].toString(); var path = yoono.utils.splitPath(pathAsStr); if ('/' != pathAsStr) { if(!(folder = this.foldersList[pathAsStr])) { folder = this.getOrCreateFolder(path); this.foldersList[pathAsStr] = folder; } } else { folder = gCurrentTree; } // If private folder received from server already exists as not private if(("CLIENT" == privateFolder) && Server2Browser.privateIncomingFoldersList[pathAsStr] && Server2Browser.privateIncomingFoldersList[pathAsStr].exists) { // Record it as 'to be added to private folders' var id = folder.getId(); if('/' == pathAsStr) id = gCurrentTree.id; log.debug('New private folder: Path:' + pathAsStr + ' id: ' + id + ' title: ' + title + ' url: ' + url); Server2Browser.newPrivateFolders[id] = true; // delete it from list so that later 'isAcceptable' calls won't be disturbed... // setting it public again would be impossible otherwise delete (Server2Browser.privateIncomingFoldersList[pathAsStr]); } //log.info('Checking Path:' + pathAsStr + ' title: ' + title + ' url: <' + url + '> folder : \n'+folder.getName()); if (!url) return false; for each(var c in folder.getChildren()) { if (c.getUrl() == url) { return false; } } if (type == 'b') { gBrowserBkm.createBookmark( title, url, folder); } else if (type == 'l') { gBrowserBkm.createLivemark( title, url, folder); } this.resp.added++; return true; }, getOrCreateFolder : function(path) { if (path.length == 1) { return gCurrentTree; } if (path[(path.length - 1)] == TOOLBARNAME && path.length == 2) { return gToolbarNode; } var parent = this.getOrCreateFolder(path.slice(0, -1)); var lastFolderName = path[path.length - 1]; for each(var c in parent.getChildren()) { if (c.getName() == lastFolderName && c.isFolder()) { return c; } } return gBrowserBkm.createFolder(lastFolderName, parent); }, removeBkm : function (node) { //log.debug('remove node (name='+node.getName()+',path='+node.getPath().toServer + ')'); var parent = node.getParent(); gBrowserBkm.remove(node); if (parent.isFolder() && parent.getChildren().length==0) gBrowserBkm.remove(parent); }, } /////////////////////////////// /////////////////////////////// // bkmNode // :: each bookmark or folder are mapped to one bkmNode // :: generate Unique ID // Public common interface bkmNode.prototype.getId = function () { return this.id; } bkmNode.prototype.getUrl = function() { return this.url; } bkmNode.prototype.getParent = function () { return this.parent; } bkmNode.prototype.getName = function () { return this.name; } bkmNode.prototype.getChildren = function () { return this.childsList; } bkmNode.prototype.isPrivate = function () { return this.private; } bkmNode.prototype.isChildOfPrivate = function () { return this.childOfPrivate; } // ""Protected"" interface (used only by bookmark service) bkmNode.prototype.setPrivate = function (newStatus) { if (newStatus!=this.private) { this.private=newStatus; if(this.private) { nbBkms --; } else { nbBkms ++; } this.resetSyncid(); function traverse(node) { for each(var child in node.childsList) { if (!child.isPrivate()) { child.childOfPrivate=newStatus; traverse(child); } } } traverse(this); } } bkmNode.prototype.addChild = function (child) { nbBkms ++ ; this.childsList.push(child); this.resetSyncid(); } bkmNode.prototype.clone = function () { var clone={}; for(var i in this) { clone[i]=this[i]; } return clone; } bkmNode.prototype.remove = function () { this.parent.removeChild(this); delete gBkmById[this.id]; nbBkms --; if (this.isBookmark() || this.isLive()) delete gBkmByUrl[this.getUrl()]; if (this.isFolder()) { for each(c in this.childsList) c.remove(); } delete this.childsList; } bkmNode.prototype.toString = function (indent) { if (!indent) indent=""; if (this.isFolder()) { var str=indent+"[ #"+this.id+" sync:"+this.syncid+" t:"+this.type+", "+(this.private?"private":"public")+" , name:"+this.getName()+"\n"; for(var i in this.childsList) { str+=this.childsList[i].toString(indent+" "); } str+=indent+"]\n"; return str; } else { var name=this.getName(); return indent+"( #"+this.id+" sync:"+this.syncid+" t:"+this.type+" , name:"+(name?name.substr(0,50):"")+" )\n"; } } bkmNode.prototype.getAllChildren = function () { if (!this.isFolder()) return []; var children = this.childsList; for(var i in this.childsList) { children=children.concat(this.childsList[i].getAllChildren()); } return children; } /* * @ return : un objet path comprenant : * .toSync : une chaine qui sera utilisee pour calculer le sync-id * .toServer : une chaine qui sera envoyee au serveur */ bkmNode.prototype.getPath = function() { if (!this.path) { if (!this.parent) { // fin de la recursion this.path = { toSync : '/', toServer : '/', }; } else { var pPath=this.parent.getPath(); this.path = {toSync:pPath.toSync, toServer:pPath.toServer}; if (this.isFolder()) { if (this.parent.parent) { // If the parent is not the root node this.path.toSync += '/'; this.path.toServer += '/'; } this.path.toSync += yoono.utils.normalize(this.getName()).toLowerCase(); this.path.toServer += yoono.utils.escapePath(this.getName()); } } } return this.path; } bkmNode.prototype.getSyncid = function () { //log.debug(" get syncid : "+this.id+" "+this.syncid+" "+this.name); if (!this.syncid) { var aPath = this.getPath().toSync; if (this.isFolder()) { var str = aPath + 'F'; var sum = this.stringToSum(aPath); var urlExist = {}; for (var i = this.childsList.length; i--> 0;) { var item= this.childsList[i]; var url = item.getUrl(); if (item.isAcceptable() && !urlExist[url]) { sum += item.getSyncid(); if (url) { urlExist[url] = true; } } } } else { var aName = this.getName().substring(0,124); var aHref = this.getUrl(); aName = yoono.utils.normalize(aName); if( aHref ) { // patch pour Firefox 2.0 (microsummaries url=null) var aUrl = aHref.substring(0,255); var str = aPath.concat( aName.toLowerCase() , aUrl , 'B'); //var str=[aPath,aName.toLowerCase(),aUrl,'B'].join(""); sum = this.stringToSum(str); } } this.syncid = sum; } return this.syncid; } var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter); // we use UTF-8 here, you can choose other encodings. converter.charset = "UTF-8"; // Inspiration : http://www.webtoolkit.info/javascript-utf8.html bkmNode.prototype.stringToSum = function (str) { var a = 1; var b = 0; var l = 0; var result = {}; var data = converter.convertToByteArray(str, result); l=result.value; for (var i = 0/*, j=0*/ ; i < l; /*j++*/) { // Convert from unicode array to "charCodeAt" values var code = -1; if (data[i]<128) { code=data[i]; i++; } else if ((data[i] > 191) && (data[i] < 224)) { code=(((data[i] & 31) << 6) | (data[i+1] & 63)); i+=2; } else { code=((data[i] & 15) << 12) | ((data[i+1] & 63) << 6) | (data[i+2] & 63); i+=3; } //if (code!=str.charCodeAt(j)) // log.debug(this.id+"@"+i+" : "+str[i]+" -> "+code+"/"+str.charCodeAt(j)+" ["+str.length+"/"+l+"]"); a += code; b += a + code; } return (a % 65521) + 65536 * (b % 65521); } /* bkmNode.prototype.stringToSum= function(buf) { var adler = 1; var len = buf.length; var NMAX = 3854; var BASE = 65521; var s1 = adler & 0xffff; var s2 = (adler & 0xffff0000) / 65536; var k; var bpos = 0; while (len > 0) { k = len < NMAX ? len : NMAX; len -= k; while (k > 0) { s1 = (s1 + buf.charCodeAt(bpos)) & 0xffffffff; s2 = (s2 + s1) & 0xffffffff; bpos += 1; k -= 1; } s1 = s1 % BASE; s2 = s2 % BASE; } return 2; return ((s2 & 0x0000ffff)*65536)+s1; } */ bkmNode.prototype.removeChild = function(node) { if (!this.isFolder() || !this.childsList) return; for(i=0;i<this.childsList.length;i++){ if(node==this.childsList[i]) { this.childsList.splice(i, 1); nbBkms --; break; } } this.resetSyncid(); } bkmNode.prototype.resetSyncid = function () { var pNode = this; while (pNode) { pNode.syncid = 0; pNode = pNode.getParent(); } } bkmNode.prototype.isAcceptable = function () { if (this.getPath().toServer.length > MAX_PATH_LENGTH) return false; if (this.isChildOfPrivate()) return false; return true; /* // if folder, check if it exists in incoming private folder list so that it won't // be erased even if wasn't private before import if (this.isFolder()) { var key = this.getPath().toServer; if(Server2Browser.privateIncomingFoldersList[key] && !this.private) { log.debug('Found existing public folder ' + key); Server2Browser.privateIncomingFoldersList[key].exists = true; this.private = true; return false; } } */ } /* * traverse recursivement le noeud * l'objet peut contenir une fonction onNode * a etre appliquee a chaque noeud * par d�faut, on n'applique pas cette m�thode * � un noeud non acceptable, mais on peut forcer */ bkmNode.prototype.traverseNode = function (obj, force) { if (!this.isAcceptable() && !force) return; obj.onNode(this); if (this.isFolder()) { for (var i = this.childsList.length ; i-- > 0; ) { this.childsList[i].traverseNode(obj, force); } } } /* @ param node : un noeud de l'arborescence @ param xml : un objet xml @ return : l'objet xml qui contient les attributs title, path, et url correspondants au noeud xml est modifie dans cette fonction */ bkmNode.prototype.setAllAttributes = function(xml) { this.setAttribute('title' , xml); this.setAttribute('url', xml); this.setAttribute('path', xml); return xml; } /* @ param node : un noeud de l'arborescence @ param xml : un objet xml @ param attribute : une chaine representant l'attribut a traiter (path, url ou title) @ return : l'objet xml qui contient l'attribut correspondant au noeud xml est modifie dans cette fonction */ bkmNode.prototype.setAttribute = function (attribute, xml) { switch (attribute) { case ('title') : if (this.getName()) { // on met un marqueur sur les live-marks pour pouvoir les reconnaitre lors d'un import xml.@title = this.getName() + ((this.isLive()) ? LIVETAG : ''); } break; case ('path') : xml.@path = this.getPath().toServer; break; case ('url') : if (this.getUrl()) { xml.@url = this.getUrl(); } break; } return xml; } //////////////////////////////////// ////////////////////////////////////// // privateMgr // :: manage private bookmarks list in %profile%/$YOONO_DIR/$PRIVATEFILE // :: Display locks in front of private folders // :: Call server to synchronise var privateMgr = { file : DIRSERVICE.get("ProfDS", CI.nsIFile), init : function () { try { // Releases before 3.0.6 wrote the locked folders file in the user chrome directory // Newer releases write the locked file in the yoono directory // If the file exists at the old location, move it first var oldFile = DIRSERVICE.get("UChrm", CI.nsIFile); oldFile.append(PRIVATEFILE); if (oldFile.exists()) { var newFile = DIRSERVICE.get('ProfDS', CI.nsIFile); newFile.append(YOONO_DIR); oldFile.moveTo(newFile,PRIVATEFILE); } this.file.append(YOONO_DIR); this.file.append(PRIVATEFILE); this.readList(); } catch(e) { log.exception(e); } }, uninstall : function () { try { var file = DIRSERVICE.get("ProfDS", CI.nsIFile); file.append(YOONO_DIR); file.append(PRIVATEFILE); if( file.exists() ) { file.remove(true); } } catch(e) {} }, appendToList : function (newPrivateFolders) { try { var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream); // 0x02 | 0x10 : WRONLY, APPEND or CREATE outputStream.init(this.file, 0x02 | 0x08 | 0x10 , 0644, 0); for (var id in newPrivateFolders) { outputStream.write(id + '\n' , id.length + 1); } outputStream.close(); } catch(e) { log.exception(e); } }, addToList : function (id) { var node = gBkmById[id]; if(!node) return log.error('NULL : ' + id); if (node.isPrivate()) return log.debug("node is already private"); if (node.isFolder()) { node.traverseNode({onNode:function(child) { if (!child.isFolder()) yoono.server.launch('remove-link', child); }}); node.setPrivate(true); yoono.server.launch('add-private-folder', node); } else { log.error("add bkm to private list ? (must be called only on folders)"); } this.saveList(); log.info(node.getName() + ' becomes private'); }, removeFromList : function (id) { var node = gBkmById[id]; if(!node) return log.error('NULL : ' + id) ; if (!node.isPrivate()) return log.error("node is not private : "+id); node.setPrivate(false); log.info(node.getName() + ' set public by user'); if (node.isFolder()) { yoono.server.launch('remove-private-folder', node); node.traverseNode({onNode:function(child) { if (!child.isFolder()) { yoono.server.launch('add-link', child); } }}); } else { log.error("remove bkm from private list ? (must be called only on folders)"); } this.saveList(); }, /* Called on a private folder that the server says is now public * folder must be removed from private folders list */ removeFolderFromList : function (id) { var node = gBkmById[id]; node.setPrivate(false); log.info(node.getName() + ' set public by server'); this.saveList(); node.resetSyncid(); }, // cette fonction doit etre appelee une fois que les marque-pages ont ete charges readList : function() { if (!this.file.exists()) { return; } var istream = CL["@mozilla.org/network/file-input-stream;1"].createInstance(CI.nsIFileInputStream); istream.init(this.file, 0x01, 0444, 0); istream.QueryInterface(CI.nsILineInputStream); var _uptodate = true; var line = {}; var hasmorelines; do { hasmorelines = istream.readLine(line); // on elimine les lignes vides, sources d'erreurs if (line.value) { // on ne garde que si la ressource existe reelement if (gBkmById[line.value]) { gBkmById[line.value].setPrivate(true); // sinon, on met le fichier a jour } else { _uptodate = false; } } } while(hasmorelines); istream.close(); if (!_uptodate) { this.saveList(); } }, clearList : function() { log.debug('Clearing private folders file'); var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream); outputStream.init(this.file, 0x02 | 0x08 | 0x20 , 0644, 0); outputStream.close(); }, saveList : function () { var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream); outputStream.init(this.file, 0x02 | 0x08 | 0x20 , 0644, 0); for (var id in gBkmById) { if (gBkmById[id].isPrivate()) { outputStream.write(id + '\n' , id.length + 1); } } outputStream.close(); } } //////////////////////////////////// var YOONO_BKM = new Bookmarks();